package server.controller;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Iterator;

import server.util.Logger;
import shared.messages.IncorrectNameMessage;
import shared.messages.Message;
import shared.messages.NameOkMessage;
import shared.player.Player;

/**
 * The Class ClientSession
 * - used to store information about the clients session.
 */
public class ClientSession extends Thread {
	
	/** The socket used for connecting the client. */
	private Socket socket;
	
	/** The list of connected clients. */
	private ActiveSessions activeSessions;
	
	/** Incoming message reader. */
	private BufferedReader netIn;
	
	/** Outgoing object stream */
	private ObjectOutputStream netOut;
	
	/** Boolean value to show if the client is in a game (true if in game). */
	private boolean inGame = false;
	
	/** True if player is ready to start the game. */
	private boolean ready = false;
	
	/** The logger for logging error and server messages. */
	private Logger logger;
	
	/** The player connected to the clientSession. */
	public Player p;

	/**
	 * Instantiates a new ClientSession.
	 *
	 * @param socket the socket used for connecting the client
	 * @param activeSessions the list of connected clients
	 * @param logger the logger for logging server and error messages
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	public ClientSession(Socket socket, ActiveSessions activeSessions, Logger logger) throws IOException {
		this.socket = socket;
		this.activeSessions = activeSessions;
		this.logger = logger;
		start();
	}

	/** Manages the communication between
	 * the client and the server
	 * @see java.lang.Thread#run()
	 */
	public void run() {
		try {
			netOut = new ObjectOutputStream(new BufferedOutputStream(socket.getOutputStream()));
			netOut.flush();
			netIn = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));
		} catch (IOException e1) {
			logger.logError("Error creating datastreams. In thread: " + this);
			activeSessions.removeFromList(this);
		}
		logger.log("ClientSession " + this + " starts...");
		while (true) {
			String nameMsg = null;
			try {
				nameMsg = netIn.readLine();
			} catch (IOException e) {
				logger.logError("Error reading from client. Client disconnected");
				closeSocket();
			}
			if (nameMsg != null) {
				if(!nameCheck(nameMsg)){
					sendMessage(new IncorrectNameMessage());
					logger.logError("Incorrect name message");
					closeSocket();
					break;
				}else{
				sendMessage(new NameOkMessage());
				logger.log("Name ok in thread: " + this);
				this.setName(nameMsg);
				break;
				}
			}
		}
		try {
			activeSessions.addSession(this);
			while (true) {
				String inMsg = null;
				try {
					inMsg = netIn.readLine();
				} catch (IOException e) {
					//if player disconnects before the game starts, but all player slots are already filled
					if(p != null){
						p.disconnected = true;
						ready = true;
					}
					logger.logError("Error reading from client. Client disconnected");
					closeSocket();
					break;
				}
				String[] pieces = inMsg.split(" ");
				if(pieces[0].equals("ACK")){
					p.readyForMove = true;
					p.setDir(Integer.parseInt(pieces[1]));
				}else if (inMsg.equals("READY")) {
					ready = true;
				}else if(inMsg.equals("REPLAY")){
					p = null;
					inGame = false;
					ready = false;
				}else if(inMsg.equals("DC")){
					break;
				}
			}
		} finally {
			closeSocket();
			logger.log("Thread " + this + " disconnected");
		}
	}

	/**
	 * Name check
	 * - checks if the name is already in use
	 *
	 * @param nameMsg the entered name
	 * @return true, if name is not in use
	 */
	private boolean nameCheck(String nameMsg) {
		synchronized(activeSessions){
			Iterator<ClientSession> itr = activeSessions.iterator();
			while(itr.hasNext()){
				ClientSession cli = itr.next();
				if(nameMsg.equals(cli.getName())){
					return false;
				}
			}
			return true;
		}
	}

	/**
	 * Send message to the client.
	 *
	 * @param msg the message to be sent
	 */
	public void sendMessage(Message msg) {
		try {
			if (!socket.isClosed()) {
				netOut.writeObject(msg);
				netOut.flush();
			} else {
				throw new IOException();
			}
		} catch (IOException eee) {
			closeSocket();
		}
	}
	
	/**
	 * Closes the socket and removes the ClientSession from
	 * the active sessions list.
	 */
	private void closeSocket(){
		activeSessions.removeFromList(this);
		try {
			socket.close();
		} catch (IOException ee) {
			logger.log("Socket closed in thread: " + this);
		}
	}
	
	/**
	 * Gets the boolean value whether the clientsession
	 * is currently used in some game.
	 *
	 * @return the value of inGame
	 */
	public boolean getInGame(){
		return inGame;
	}
	
	/**
	 * Sets the value of the inGame to true.
	 */
	public void setInGame(){
		inGame = true;
	}
	
	/**
	 * Gets the boolean value whether the clientsession is
	 * ready to start the game.
	 *
	 * @return the value of ready
	 */
	public boolean getReady(){
		return ready;
	}
}